#!/usr/bin/env ruby
	
	#
	# Depends on Kou's RSS Parser module (http://raa.ruby-lang.org/list.rhtml?name=rss).
	#
	# Potential enhancements:
	#
	# - preference of whether the pictures are listed latest first, or
	# oldest first.
	# - option to always show the latest picture after a refresh
	#
	
	require 'fox16'
	require 'open-uri'
	
	begin
	require 'rss/parser'
	require 'rss/2.0'
	rescue LoadError
	require 'fox16/missingdep'
	MSG = <<EOM
	Sorry, this example depends on the RSS extension. Please
	check the Ruby Application Archives for an appropriate
	download site.
	EOM
	missingDependency(MSG)
	end
	
	include Fox
	
	class WhatAQuietWindow < FXMainWindow
	
	RSS_FEED_URL = "http://whytheluckystiff.net/quiet/quiet.xml"
	DEFAULT_REFRESH_DELAY = 60 # in minutes
	
	def initialize(app)
	# Invoke base class initialize first
	super(app, "What a Quiet Stiff", :opts => DECOR_ALL, :width => 850, :height => 600, :padLeft => 0, :padRight => 0)
	
	# Icons for list items
	File.open("icons/bluebullet14x14.gif", "rb") do |f|
	bytes = f.read
	@itemIcon = FXGIFIcon.new(getApp(), bytes)
	end
	File.open("icons/transpbullet14x14.gif", "rb") do |f|
	bytes = f.read
	@transpIcon = FXGIFIcon.new(getApp(), bytes)
	end
	
	# Menubar
	menuBar = FXMenuBar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
	
	# File menu
	fileMenu = FXMenuPane.new(self)
	saveCmd = FXMenuCommand.new(fileMenu, "Save selected image...")
	saveCmd.connect(SEL_COMMAND, method(:onCmdSave))
	saveCmd.connect(SEL_UPDATE, method(:onUpdSave))
	FXMenuCommand.new(fileMenu, "Preferences...").connect(SEL_COMMAND, method(:onCmdPreferences))
	FXMenuCommand.new(fileMenu, "&Quit\tCtrl+Q").connect(SEL_COMMAND, method(:onQuit))
	FXMenuTitle.new(menuBar, "&File", nil, fileMenu)
	
	# Help menu
	helpMenu = FXMenuPane.new(self)
	FXMenuTitle.new(menuBar, "&Help", nil, helpMenu, LAYOUT_RIGHT)
	aboutCmd = FXMenuCommand.new(helpMenu, "&About...")
	aboutCmd.connect(SEL_COMMAND) do
	FXMessageBox.information(self, MBOX_OK, "About This Program",
	"What a Quiet Stiff\nA Sliding Surface for Found Imagery\nCourtesy of http://whytheluckystiff.net")
	end
	
	# Respond to window close
	self.connect(SEL_CLOSE, method(:onQuit))
	
	# Main contents area is split left-to-right.
	splitter = FXSplitter.new(self, LAYOUT_FILL_X|LAYOUT_FILL_Y)
	
	# Put the list in a sunken frame
	listFrame = FXVerticalFrame.new(splitter,
	FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y,
	:padding => 00)
	
	# List of items appears on the left.
	@itemList = FXList.new(listFrame, :opts => LAYOUT_FILL_X|LAYOUT_FILL_Y)
	@itemList.numVisible = 12
	@itemList.connect(SEL_COMMAND) do |sender, sel, itemIndex|
	@showLinkedImage = false
	getApp().beginWaitCursor do
	setImage(@itemList.getItemData(itemIndex))
	@itemList.setItemIcon(itemIndex, @transpIcon)
	end
	end
	
	# Sunken border for image widget
	imagebox = FXHorizontalFrame.new(splitter,
	FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y,
	:padding => 0)
	
	# Make image widget
	@imageview = FXImageView.new(imagebox, :opts => LAYOUT_FILL_X|LAYOUT_FILL_Y)
	@imageview.enable
	@imageview.connect(SEL_LEFTBUTTONRELEASE) { toggleImage }
	
	# Cache previously viewed images in a Hash
	@cache = {}
	@showLinkedImage = false
	
	# Start out with the current feed's contents.
	refreshList
	end
	
	# Return true if an item is selected, false otherwise.
	def itemSelected?
	begin
	@itemList.itemSelected?(@itemList.currentItem)
	rescue IndexError
	false
	end
	end
	
	#
	# Enable or disable the "Save Image" command, depending on
	# whether or not any items are selected.
	#
	def onUpdSave(sender, sel, ptr)
	if itemSelected?
	sender.handle(self, FXSEL(SEL_COMMAND, ID_ENABLE), nil)
	else
	sender.handle(self, FXSEL(SEL_COMMAND, ID_DISABLE), nil)
	end
	end
	
	# Save the currently selected image to a file.
	def onCmdSave(sender, sel, ptr)
	saveDialog = FXFileDialog.new(self, "Save Image")
	saveDialog.filename = @itemList.getItemText(@itemList.currentItem)
	if saveDialog.execute != 0
	if File.exists? saveDialog.filename
	if MBOX_CLICKED_NO == FXMessageBox.question(self, MBOX_YES_NO,
	"Overwrite Image", "Overwrite existing image?")
	return 1
	end
	end
	getApp().beginWaitCursor do
	FXFileStream.open(saveDialog.filename, FXStreamSave) do |stream|
	@imageview.image.restore
	@imageview.image.savePixels(stream)
	end
	end
	end
	end
	
	# Display the Preferences dialog box.
	def onCmdPreferences(sender, sel, ptr)
	refreshDelayTarget = FXDataTarget.new(@refreshDelay)
	
	prefsDialog = FXDialogBox.new(self, "Preferences", :padding => 2)
	
	buttons = FXHorizontalFrame.new(prefsDialog, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X)
	FXFrame.new(buttons, LAYOUT_FILL_X)
	FXButton.new(buttons, "Cancel", nil, prefsDialog, FXDialogBox::ID_CANCEL,
	LAYOUT_SIDE_RIGHT|LAYOUT_CENTER_Y|FRAME_RAISED|FRAME_THICK,
	:padLeft => 20, :padRight => 20, :padTop => 4, :padBottom => 4)
	FXButton.new(buttons, "OK", nil, prefsDialog, FXDialogBox::ID_ACCEPT,
	LAYOUT_SIDE_RIGHT|LAYOUT_CENTER_Y|FRAME_RAISED|FRAME_THICK,
	:padLeft => 30, :padRight => 30, :padTop => 4, :padBottom => 4)
	
	FXHorizontalSeparator.new(prefsDialog, SEPARATOR_GROOVE|LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X)
	
	contents = FXMatrix.new(prefsDialog, 2, MATRIX_BY_COLUMNS|LAYOUT_FILL_X|LAYOUT_FILL_Y)
	FXLabel.new(contents, "Refresh Delay (minutes):", nil, LAYOUT_CENTER_Y)
	FXTextField.new(contents, 5, refreshDelayTarget, FXDataTarget::ID_VALUE,
	LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT)
	
	if prefsDialog.execute != 0
	@refreshDelay = refreshDelayTarget.value
	end
	end
	
	#
	# Given an RSS object, populate the list of images with one
	# per item in the RSS.
	#
	def populateItemList(rss)
	@itemList.clearItems
	liveItems = {}
	rss.items.each do |rssItem|
	srcURL = getSourceURL(rssItem)
	linkURL = getLinkURL(rssItem)
	itemIcon = (@cache.key?(srcURL) || @cache.key?(linkURL)) ? @transpIcon : @itemIcon
	@itemList.appendItem(rssItem.title, itemIcon, rssItem)
	liveItems[srcURL] = 1 if @cache.key?(srcURL)
	liveItems[linkURL] = 1 if @cache.key?(linkURL)
	end
	@cache.delete_if { |key, value| !liveItems.key?(key) }
	end
	
	def toggleImage
	@showLinkedImage = !@showLinkedImage
	if itemSelected?
	itemIndex = @itemList.currentItem
	getApp().beginWaitCursor do
	setImage(@itemList.getItemData(itemIndex))
	@itemList.setItemIcon(itemIndex, @transpIcon)
	end
	end
	end
	
	def setImage(rssItem)
	url = getImageURL(rssItem)
	img = @cache[url]
	if img.nil?
	img = makeImage(url)
	img.create
	@cache[url] = img
	end
	@imageview.image = img
	end
	
	def getRSSFeed(url)
	rss = nil
	open(url) do |f|
	doc = f.read
	begin
	rss = RSS::Parser.parse(doc)
	rescue RSS::InvalidRSSError
	rss = RSS::Parser.parse(doc, false)
	end
	end
	rss
	end
	
	# Return the URL listed in the src tag of the description's HTML text.
	def getSourceURL(rssItem)
	rssItem.description =~ /src="(.*?)"/
	return $1
	end
	
	# Return the URL listed in the href tag of the description's HTML text.
	def getLinkURL(rssItem)
	rssItem.description =~ /href="(.*?)"/
	return $1
	end
	
	# Return the appropriate URL given the current settings.
	def getImageURL(rssItem)
	@showLinkedImage ? getLinkURL(rssItem) : getSourceURL(rssItem)
	end
	
	def getImageData(url)
	bytes = nil
	open(url, "rb") do |f|
	bytes = f.read
	end
	bytes
	end
	
	# This is a little weak...
	def makeImage(url)
	bytes = getImageData(url)
	extension = url.split('.').last.upcase
	case extension
	when "GIF"
	FXGIFImage.new(getApp(), bytes)
	when "JPG"
	FXJPGImage.new(getApp(), bytes)
	when "PNG"
	FXPNGImage.new(getApp(), bytes)
	else
	raise "Unrecognized file extension for: #{url}"
	end
	end
	
	def resizeItemList
	maxItemSize = 0
	@itemList.each do |listItem|
	itemSize = @itemList.font.getTextWidth(listItem.text)
	maxItemSize = [maxItemSize, itemSize].max
	end
	@itemList.parent.width = maxItemSize
	end
	
	def refreshList
	# Grab the latest RSS feed
	@rss = getRSSFeed(RSS_FEED_URL)
	
	# Repopulate the list with this set of items
	populateItemList(@rss)
	end
	
	def onRefreshList(sender, sel, ptr)
	# Refresh, then re-register the timeout
	getApp().beginWaitCursor { refreshList }
	getApp().addTimeout(1000*60*@refreshDelay, method(:onRefreshList))
	end
	
	def onQuit(sender, sel, ptr)
	writeRegistry
	getApp().exit(0)
	end
	
	def readRegistry
	xx = getApp().reg().readIntEntry("SETTINGS", "x", 0)
	yy = getApp().reg().readIntEntry("SETTINGS", "y", 0)
	ww = getApp().reg().readIntEntry("SETTINGS", "width", 850)
	hh = getApp().reg().readIntEntry("SETTINGS", "height", 600)
	@refreshDelay = getApp().reg().readIntEntry("SETTINGS", "refreshDelay", DEFAULT_REFRESH_DELAY)
	end
	
	def writeRegistry
	getApp().reg().writeIntEntry("SETTINGS", "x", x)
	getApp().reg().writeIntEntry("SETTINGS", "y", y)
	getApp().reg().writeIntEntry("SETTINGS", "width", width)
	getApp().reg().writeIntEntry("SETTINGS", "height", height)
	getApp().reg().writeIntEntry("SETTINGS", "refreshDelay", @refreshDelay)
	end
	
	def create
	# Do base class create first
	super
	
	readRegistry
	
	@itemIcon.create
	@transpIcon.create
	
	# Make the item list wide enough to show the longest item
	resizeItemList
	
	# Resize main window
	# Resize main window client area to fit image size
	# resize(@imageview.contentWidth, @imageview.contentHeight)
	
	# Now show it
	show(PLACEMENT_SCREEN)
	
	# Start the updates timer
	getApp().addTimeout(1000*60*@refreshDelay, method(:onRefreshList))
	end
	end
	
	if __FILE__ == $0
	# Make application
	application = FXApp.new("WhatAQuietWindow", "FXRuby")
	
	# Make window
	window = WhatAQuietWindow.new(application)
	
	# Handle interrupts to terminate program gracefully
	application.addSignal("SIGINT", window.method(:onQuit))
	
	# Create it
	application.create
	
	# Run
	application.run
	end